---
title: Expand group membership from Active Directory, optionally recursive and optionally including disabled accounts
summary: GetUsersByGroup end-to-end process on SAIF.Services.WCF.Services.Security.SecurityService.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-22
operation_id: OP-024
trigger: Caller invokes `GetUsersByGroup` over the `Security` route.
---

# OP-024: Expand group membership from Active Directory, optionally recursive and optionally including disabled accounts

GetUsersByGroup is invoked over the `Security` route on `SAIF.Services.WCF.Services.Security.SecurityService`; this page documents the process end to end, from route dispatch to the message the caller receives.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-024 |
| **Trigger** | Caller invokes `GetUsersByGroup` over the `Security` route. |
| **Entry operation** | [`GetUsersByGroup`](../contracts/security-service.md#getusersbygroup) |
| **Owning component** | `SAIF.Services.WCF.Services.Security.SecurityService.GetUsersByGroup` |
| **Inputs** | Request message parts listed below |
| **Outputs** | A de-duplicated, ascending array of `sAMAccountName` values for the group's membership. |
| **Failure behavior** | Generic SOAP fault, per [Faults and error pipeline](../faults.md) |

## Request and response

| Direction | Element | Type | Declared in | Notes |
| --- | --- | --- | --- | --- |
| Request | `groupName` | `string` | BCL (`System`) | Directory group to expand. (Yes) |
| Request | `ldap` | `string` | BCL (`System`) | LDAP path used to open the `DirectoryEntry` for the membership search. (Yes) |
| Request | `domain` | `string` | BCL (`System`) | Domain used to resolve `groupName`. (No (default `"saif"`)) |
| Request | `recurse` | `bool` | BCL (`System`) | Whether nested groups are expanded recursively. (No (default `true`)) |
| Request | `includeDisabled` | `bool` | BCL (`System`) | Whether disabled accounts are included in the result. (No (default `false`)) |
| Response | `string[]` | `string[]` | BCL (`System`) | A de-duplicated, ascending array of `sAMAccountName` values for the group's membership. |

<!-- Types declared in `SAIF.Services.WCF.Contracts` are attributed to `SAIF.Framework.Core`, inferred from source and package references and marked `*`; their members are not enumerated because the external declaration was not read, marked `**`. -->

## Steps

1. `LogRequestDetails` logs the operation name and arguments ([BR-WCF-032](../business-rules.md#br-wcf-032-every-security-operation-logs-its-request)).
2. `new ADService()`, then `GetUsersByGroup`: open a `PrincipalContext` on the supplied `domain` (default `"saif"`) and resolve the group; a missing group returns `null`, not an empty array ([BR-WCF-035](../business-rules.md#br-wcf-035-missing-group-returns-null-not-empty-array)).
3. Open a `DirectoryEntry` on the supplied `ldap` string and run `GetMembershipRecursively` with a `DirectorySearcher` filter of `"(&(objectClass=" + objectClass + ")(memberOf=" + groupDn + "))"`, `PageSize = 1000`, `SizeLimit = 0`, sorted by `sAMAccountName` ([BR-WCF-036](../business-rules.md#br-wcf-036-group-membership-read-through-a-paged-directory-search)).
4. When `recurse` is `true`, nested groups are expanded depth-first with a visited-group list preventing cycles ([BR-WCF-037](../business-rules.md#br-wcf-037-nested-groups-expanded-recursively-without-cycles)).
5. Unless `includeDisabled`, each user's `userAccountControl` is converted to binary and the second-from-last bit tested; a set bit means disabled and the user is skipped ([BR-WCF-038](../business-rules.md#br-wcf-038-disabled-accounts-excluded-from-group-membership)).
6. Project `sAMAccountName`, then `.Distinct().OrderBy(x => x).ToArray()` ([BR-WCF-039](../business-rules.md#br-wcf-039-group-membership-is-deduplicated-and-sorted)).
7. Return.
   - On failure: a directory exception is logged by the installed error handler and surfaced as a generic SOAP fault with exception detail included.

## Process flow

The following diagram visualizes this process's control flow.

```mermaid
flowchart TD
    A["Log request details"]
    B["Resolve group via ADService"]
    B1{"Group resolved?"}
    B2["Missing group: return null,<br>not an empty array"]
    C["Search directory for group members"]
    D1{"recurse"}
    D["Expand nested groups (recursive)"]
    D2["Nested groups are not expanded"]
    E1{"includeDisabled"}
    E["Filter out disabled accounts"]
    E2["Disabled accounts are not<br>filtered out"]
    F["Deduplicate and sort account names"]
    G["Return."]
    A --> B
    B --> B1
    B1 -->|"No"| B2
    B1 -->|"Yes"| C
    B2 --> G
    C --> D1
    D1 -->|"true"| D
    D1 -->|"false"| D2
    D --> E1
    D2 --> E1
    E1 -->|"false"| E
    E1 -->|"true"| E2
    E --> F
    E2 --> F
    F --> G
```

## Transaction and side effects

| Aspect | Finding |
| --- | --- |
| Unit of work | None for this path |
| Commit point | Not applicable |
| Records written | None |
| Downstream calls | Active Directory (supplied `domain` and `ldap` path, with an unbounded `SizeLimit = 0` paged search) |
| Shared state touched | None |
| Repeat-call behavior | Idempotent read; result reflects current directory state |

## Logging

| Surface | Written content |
| --- | --- |
| `HttpContext.Current.Response.AppendToLog` (`LogRequestDetails`) | Writes IIS request-detail log lines when `LOG_REQUEST_DETAILS` is enabled |
| `ErrorLogErrorHandler` → `LogService` | Failure-path exception logging only, carrying method signature, calling user and exception detail; see [Faults and error pipeline](../faults.md) |

## Faults and error handling

| Failure | Fault type the caller receives | Produced by | Source |
| --- | --- | --- | --- |
| Directory exception | `Generic SOAP fault` | The `ErrorLogErrorHandler` installed by `[LogErrorBehavior]` | src/WCF/SAIF.Services.WCF.Host/Services/ADService.cs |

## Authorization

| Requirement | Enforced by | Source | Applies to |
| --- | --- | --- | --- |
| [None in code](../authentication-authorization.md#authorization-enforcement) | IIS authentication on the deployment (see [Activation and hosting](../activation-hosting.md)) | `src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs` | This operation |

## Related documentation
- [Index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Faults and error pipeline](../faults.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Dependencies](../dependencies.md)
- [Configuration](../configuration.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Data model](../data-model.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- src/WCF/SAIF.Services.WCF.Host/Services/ADService.cs
- src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs
